package com.goldman.aplt.base.core;

import com.fasterxml.jackson.databind.JsonNode;
import com.goldman.aplt.base.constant.ApConstants;
import com.goldman.aplt.base.core.type.ComCoreAcct;
import com.goldman.aplt.base.exception.BankBusiException;
import com.goldman.aplt.base.exception.BusiErrorCode;
import com.goldman.aplt.base.interceptor.ApltExceptionInterceptor;
import com.goldman.aplt.base.type.BaseEnumType;
import com.goldman.aplt.base.type.CmErrorCode;
import com.goldman.aplt.entity.AppErrorMapping;
import com.goldman.aplt.service.AppErrorMappingService;
import com.goldman.aplt.service.parameter.AppSystemParameterService;
import com.goldman.aplt.util.ApSeqUtil;
import com.goldman.aplt.util.CommUtil;
import com.goldman.aplt.util.HttpUtil;
import com.goldman.aplt.util.JsonNodeUtil;
import com.goldman.base.common.sys.exception.BusinessException;
import com.goldman.http.common.s2s.S2SOption;
import com.goldman.http.common.s2s.S2SResponse;
import com.goldman.http.common.s2s.util.JsonUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.goldman.aplt.base.constant.ApConstants.*;

/**
 * 核心请求工具类
 */
@Component
@Slf4j
public class CoreUtil {
    @Autowired
    private AppSystemParameterService appSystemParameterService;
    @Autowired
    private AppErrorMappingService errorMappingService;
    private static final String SUCC_STATUS = "S";
    private static final String PREFIX = "/";
    @Autowired
    private StringRedisTemplate redisTemplate;

    public static final String[] FIN_TRANPATH = new String[]{"rb/fin/channel/sigle/account", "rb/fin/current/tra"};


    public CoreRespone post(String tranPath, Object body) {
        return post(tranPath, body, null, "0", "-1", true);
    }

    public CoreRespone post(String tranPath, Object body, String callSeq) {
        return post(tranPath, body, callSeq, "0", "-1", true);
    }

    public CoreRespone post(String tranPath, Object body, boolean throwError) {
        return post(tranPath, body, null, "0", "-1", throwError);
    }
    public CoreRespone post(String tranPath, Object body, String callSeq, boolean throwError) {
        return post(tranPath, body, callSeq, "0", "-1", throwError);
    }

    public CoreRespone post(String tranPath, Object body, String currentNum, String totalNum, boolean throwError) {
        return post(tranPath, body, null, currentNum, totalNum, throwError);
    }

    public CoreRespone post(String tranPath, Object body, String callSeq, String currentNum, String totalNum, boolean throwError) {
        log.info(" CoreRequest.post begin >>>>>>>>>>>>>>>>");

        if(CommUtil.isNull(callSeq)) {
            callSeq = ApSeqUtil.genSeq(ApConstants.CALL_SEQ_CODE);
        }

        Map<String, Object> req = new HashMap<>();
        CoreReqSysHeader sysHeader = CoreReqSysHeader.builder()
                .seqNo(callSeq)
                .subSeqNo(callSeq)
                .tranDate(CommUtil.getComputerDate())
                .branchId(ApConstants.BRANCH_CODE)
                .userId(CommUtil.getTrxnEnvs().getSystem_id().equals(BaseEnumType.E_SYSTEM_ID.GCB.getValue()) ? ApConstants.CORE_CORPOR_USER : ApConstants.CORE_USER)
                .tranTimestamp(System.currentTimeMillis()+"")
                .sourceType(CommUtil.getTrxnEnvs().getSystem_id().equals(BaseEnumType.E_SYSTEM_ID.GCB.getValue()) ? ApConstants.CORE_CORPOR_SOURCE_TYPE : ApConstants.CORE_SOURCE_TYPE)
                .programId("3902")
                .userLang("AMERICAN/ENGLISH")
                .tranMode("ONLINE")
                .build();
        req.put("sysHead", sysHeader);
        req.put("body", body);

        currentNum = String.valueOf((Integer.parseInt(currentNum) - 1) * Integer.parseInt(totalNum));

        CoreReqAppHeader appHeader = CoreReqAppHeader.builder()
                .pgupOrPgdn("1")
                .totalNum(totalNum)
                .currentNum(currentNum)
                .totalFlag("E")
                .totalRows(null)
                .build();

        if(!totalNum.equals("-1")) {
            appHeader.setPgupOrPgdn(null);
        }
        req.put("appHead", appHeader);

        tranPath = replace(tranPath);
        String url = appSystemParameterService.getValue(ApConstants.CORE_URL_KEY);
        S2SResponse<Object> response = HttpUtil.post(url+PREFIX+tranPath, JsonUtil.toJsonStr(req), callSeq, S2SOption.builder().contentType(S2SOption.ContentType.JSON).timeout(CALL_OUT_TIMEOUT).build());

        if(!response.isHTTPSuccess()) {
            log.info(" CoreRequest.post failure !response.isHTTPSuccess() <<<<<<<<<<<<<<<<<<<<<");
            return errorProcess(throwError, response.getHttpStatus()+"", "http error", response.getHttpStatus() == -1, true);
        }

        if(!response.getResponseData().isSuccess()) {
            log.info(" CoreRequest.post failure !response.getResponseData().isSuccess() <<<<<<<<<<<<<<<<<<<<<");
            return errorProcess(throwError, response.getResponseData().getStatus().getCode()+"", response.getResponseData().getStatus().getMsg(), false, true);
        }

        JsonNode resp = JsonUtil.toTree(JsonUtil.toJsonStr(response.getResponseData().getData()));
        if(!resp.get("sysHead").get("retStatus").asText().equals(SUCC_STATUS)) {
            JsonNode ret = resp.get("sysHead").findValue("ret").get(0);

            //金融交易超时处理
            if(JsonNodeUtil.toString(ret.get("retCode")).equals(CORE_TIMEOUT_CODE)
                    && CommUtil.in(tranPath, Arrays.asList(FIN_TRANPATH))) {
                return finTrxnToutProcess(callSeq, resp.get("sysHead").get("runDate").asText(), throwError, JsonNodeUtil.toString(ret.get("retCode")), JsonNodeUtil.toString(ret.get("retMsg")), false, false);
            }
            log.info(" CoreRequest.post end <<<<<<<<<<<<<<<<<<<<<");
            return errorProcess(throwError, JsonNodeUtil.toString(ret.get("retCode")), JsonNodeUtil.toString(ret.get("retMsg")), false, false);
        }

        String coreDate = CommUtil.getComputerDate();
        if(CommUtil.isNotNull(resp.get("sysHead").get("runDate"))) {
            coreDate = resp.get("sysHead").get("runDate").asText();
        }

        JsonNode respAppHeadr = resp.get("appHead");

        //总行数
        String respTotalTows = CommUtil.isNotNull(respAppHeadr.get("totalRows")) ? respAppHeadr.get("totalRows").asText() : "0";
        //总页数
        int totalPages = 0;
        if(CommUtil.isNotNull(respTotalTows) && Integer.parseInt(respTotalTows) > 0){
            int totalRows = Integer.parseInt(respTotalTows);
            int pageNum = Integer.parseInt(totalNum);
            totalPages = totalRows % pageNum == 0 ? totalRows / pageNum : totalRows / pageNum + 1;
        }

        log.info(" CoreRequest.post end <<<<<<<<<<<<<<<<<<<<<");
        return CoreRespone.builder().body(resp.get("body")).coreDate(coreDate)
                .currentNum(currentNum)
                .totalRows(respTotalTows)
                .totalNum(totalPages+"")
                .isSuccess(true).isTimeOut(false).isSystemError(false)
                .build();
    }

    private CoreRespone finTrxnToutProcess(String callSeq, String coreDate, boolean throwError, String errCode, String errMsg, boolean isTimeOut, boolean isSystemError) {
        log.info(" finTrxnToutProcess start <<<<<<<<<<<<<<<<<<<<<");
        int retryCount = 1;
        //最多重试5次
        while (retryCount <= 5) {
            try {
                Thread.sleep(1000 * 5);
                CoreRespone resp = queryCoreTxnStatus(callSeq, coreDate);
                if(resp.isSuccess() && CommUtil.isNotNull(resp.getBody().get("acctResult"))) {
                    String acctResult = JsonNodeUtil.toString(resp.getBody().get("acctResult"));
                    if(acctResult.equals("P")) {
                        retryCount = retryCount + 1;
                        continue;
                    }

                    if(acctResult.equals("S")) {
                        return CoreRespone.builder().body(resp.getBody()).coreDate(coreDate)
                                .currentNum("0")
                                .totalRows("0")
                                .totalNum("0")
                                .isSuccess(true).isTimeOut(false).isSystemError(false)
                                .build();
                    }
                    //交易结果是失败的
                    return errorProcess(throwError, errCode, errMsg, isTimeOut, isSystemError);
                }
            } catch (InterruptedException e) {
                log.error("finTrxnToutProcess InterruptedException!", e);
            } catch (Exception e) {
                log.error("finTrxnToutProcess Exception!", e);
            }
            retryCount = retryCount + 1;
        }
        log.error("Core accting transaction timeout processing failure please process in a timely manner! callseq: {}", callSeq);
        log.info(" finTrxnToutProcess end <<<<<<<<<<<<<<<<<<<<<");
        return errorProcess(throwError, errCode, errMsg, isTimeOut, isSystemError);
    }


    private CoreRespone errorProcess(boolean throwError, String errCode, String errMsg, boolean isTimeOut, boolean isSystemError) {

        String retmsg = ApltExceptionInterceptor.DEFAULT_ERROR_MSG.replace("ERRORCODE", "["+CmErrorCode.CORE_SYS_ERROR.getCode()+"]"+errCode);
        if(errCode.equals(CORE_TIMEOUT_CODE)) {
            isTimeOut = true;
        }
        if(throwError) {

            AppErrorMapping appErrorMapping= errorMappingService.selOne(errCode, "en-US");
            if(appErrorMapping != null) {
                retmsg = appErrorMapping.getMessage();
            }

            throw new BankBusiException(CmErrorCode.CORE_SYS_ERROR, "核心系统异常！errMsg:"+errMsg, retmsg);

        } else {
//            if(errCode.startsWith("0") || errCode.startsWith("9")) {
//                errMsg = CmErrorCode.CORE_SYS_ERROR.getMsg();
//            }
            return CoreRespone.builder().errorCode(errCode)
                    .errorMsg(errMsg)
                    .isSuccess(false).isTimeOut(isTimeOut).isSystemError(isSystemError).build();
        }
    }

    public void throwError(String errCode, String errMsg) {
        String retmsg = ApltExceptionInterceptor.DEFAULT_ERROR_MSG.replace("ERRORCODE", "["+CmErrorCode.CORE_SYS_ERROR.getCode()+"]"+errCode);
        AppErrorMapping appErrorMapping= errorMappingService.selOne(errCode, "en-US");
        if(appErrorMapping != null) {
            retmsg = appErrorMapping.getMessage();
        }
        throw new BankBusiException(CmErrorCode.CORE_SYS_ERROR, "核心系统异常！errMsg:"+errMsg, retmsg);
    }

    private static String replace(String tranPath) {
        if(tranPath.startsWith(PREFIX)) {
            tranPath = tranPath.replaceFirst(PREFIX, "");
        }
        if(tranPath.endsWith(PREFIX)) {
            tranPath = tranPath.substring(0, tranPath.length() - 1);
        }
        return tranPath;
    }


    public CoreRespone reversal(String channelSeqNo, String reversalReason, boolean throwError) {
        Map<String, Object> req = new HashMap<>();
        req.put("channelSeqNo", channelSeqNo);
        req.put("subSeqNo", channelSeqNo);
        req.put("wipeAccount", "N");
        req.put("reversalReason", reversalReason);
        req.put("skipCheckBranchFlag", "Y");
        return post("/rb/rev/tran/common", req, throwError);
    }


    public CoreRespone queryCoreTxnStatus(String channelSeqNo, String subSeqNo, String trxnDate, String coreDate, boolean throwError) {
        Map<String, Object> req = new HashMap<>();
        req.put("channelSeqNo", channelSeqNo);
        req.put("subSeqNo", subSeqNo);
        req.put("channelTranDate", trxnDate);
        if(CommUtil.isNotNull(coreDate)) {
            req.put("tranDate", coreDate);
        }
        return post("/rb/inq/tran/status", req, throwError);
    }

    public CoreRespone queryCoreTxnStatus(String channelSeqNo, String subSeqNo, String trxnDate, boolean throwError) {
        return queryCoreTxnStatus(channelSeqNo, subSeqNo, trxnDate, null, throwError);
    }


    public CoreRespone qryAcct(String acctNo) {
        return qryAcct(acctNo, BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
    }

    /**
     * 账号信息查询（四要素）
     * @param acctNo
     * @return
     */
    public CoreRespone qryAcct(String acctNo, String acctSeqNo) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("baseAcctNo", acctNo);
        req.put("acctSeqNo", acctSeqNo);
        req.put("ccy", ApConstants.BASE_CCY);

        CoreRespone resp = post("/rb/inq/channel/acct", req, false);
        if (!resp.isSuccess() && resp.getErrorCode().equals("MB4028")) {
            return null;
        } else if(!resp.isSuccess()) {
            throwError(resp.getErrorCode(), resp.getErrorMsg());
        }
        return resp;
    }

    public ComCoreAcct.AcctInfo qrySimpleAcct(String acctNo) {
        return qrySimpleAcct(acctNo, BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
    }

    /**
     * 账号信息查询（四要素）
     * @param acctNo
     * @return
     */
    public ComCoreAcct.AcctInfo qrySimpleAcct(String acctNo, String acctSeq) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("baseAcctNo", acctNo);

        if(CommUtil.isNull(acctSeq)) {
            req.put("acctSeqNo", BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
        } else {
            req.put("acctSeqNo", acctSeq);
        }

        req.put("ccy", ApConstants.BASE_CCY);
        CoreRespone resp = post("/rb/inq/channel/acct", req, false);
        if (!resp.isSuccess() && resp.getErrorCode().equals("MB4028")) {
            return null;
        } else if(!resp.isSuccess()) {
            throwError(resp.getErrorCode(), resp.getErrorMsg());
        }

        if(CommUtil.isNull(resp.getBody())) {
            return null;
        }

        JsonNode body = resp.getBody();
        ComCoreAcct.AcctInfo acctInfo = ComCoreAcct.AcctInfo.builder()
                .acctName(body.get("acctName").asText())
                .clientNo(body.get("clientNo").asText())
                .acctStatus(body.get("acctStatus").asText())
                .baseAcctNo(body.get("baseAcctNo").asText())
                .prodType(body.get("prodType").asText())
                .acctOpenDate(body.get("acctOpenDate").asText())
                .documentId(body.get("documentId").asText())
                .isOverSingleInwardLimit(false)
                .isOverDailyOutwardLimit(false)
                .isDelayProcess(false)
                .coreDate(resp.getCoreDate())
                .build();

        //企业账号没有bvn
        if(acctInfo.getClientNo().startsWith(BaseEnumType.E_CUSTTYPE.CORPORATE.getValue())) {
            acctInfo.setDocumentId("");
        }

        if(CommUtil.isNotNull(body.get("acctSeqNo"))) {
            acctInfo.setAcctSeqNo(body.get("acctSeqNo").asText());
        }

        if(CommUtil.isNotNull(body.get("ledgerBal"))) {
            acctInfo.setLedgerBal(body.get("ledgerBal").decimalValue());
        }

        if(CommUtil.isNotNull(body.get("actualBal"))) {
            acctInfo.setActualBal(body.get("actualBal").decimalValue());
        }

        if(CommUtil.isNotNull(body.get("maturityDate"))) {
            acctInfo.setMaturityDate(body.get("maturityDate").asText());
        }

        if(CommUtil.isNotNull(body.get("realRate"))) {
            acctInfo.setRealRate(body.get("realRate").decimalValue());
        }

        String classLevel = "1";
        if(CommUtil.isNotNull(body.get("classLevel"))) {
            //核心返回的calssLevel是两位数的，我们只需要用到第一位
            classLevel = body.get("classLevel").asText().substring(0,1);
            acctInfo.setLevel(JsonNodeUtil.toString(body.get("classLevel")));
        }
        acctInfo.setClassLevel(classLevel);

        return acctInfo;
    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo) {
        return qryAcctInfo(acctNo, null, null, null, null, null);
    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, Boolean isSignTfr, Boolean isFinancialCust) {
        return qryAcctInfo(acctNo, null, null, null, isSignTfr, isFinancialCust);
    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String acctSeqNo) {
        return qryAcctInfo(acctNo, null, null, null, acctSeqNo, null, null);
    }

//    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String acctSeqNo, Boolean isSignTfr, Boolean isFinancialCust) {
//        return qryAcctInfo(acctNo, null, null, null, acctSeqNo, isSignTfr, isFinancialCust);
//    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String uid, BaseEnumType.E_DEBITCREDIT debitCredit, BigDecimal tranAmt){
        return qryAcctInfo(acctNo, uid, debitCredit, tranAmt, BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue(), null, null);
    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String uid, BaseEnumType.E_DEBITCREDIT debitCredit, BigDecimal tranAmt, Boolean isSignTfr, Boolean isFinancialCust){
        return qryAcctInfo(acctNo, uid, debitCredit, tranAmt, BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue(), isSignTfr, isFinancialCust);
    }

    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String uid, BaseEnumType.E_DEBITCREDIT debitCredit, BigDecimal tranAmt, String acctSeqNo) {
        return qryAcctInfo(acctNo, uid, debitCredit, tranAmt, acctSeqNo, null, null);
    }


        /**
         * 账号信息查询（四要素）
         * @param acctNo
         * @param debitCredit 记账方向
         * @param tranAmt 交易金额
         * @return
         */
    public ComCoreAcct.AcctInfo qryAcctInfo(String acctNo, String uid, BaseEnumType.E_DEBITCREDIT debitCredit, BigDecimal tranAmt, String acctSeqNo, Boolean isSignTfr, Boolean isFinancialCust) {

        if (CommUtil.isNull(acctSeqNo)) {
            acctSeqNo = BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue();
        }

        CoreRespone resp = qryAcct(acctNo, acctSeqNo);
        if(CommUtil.isNull(resp)) {
            return null;
        }

        JsonNode body = resp.getBody();
        ComCoreAcct.AcctInfo acctInfo = ComCoreAcct.AcctInfo.builder()
                .acctName(body.get("acctName").asText())
                .clientNo(body.get("clientNo").asText())
                .acctStatus(body.get("acctStatus").asText())
                .baseAcctNo(body.get("baseAcctNo").asText())
                .prodType(body.get("prodType").asText())
                .acctOpenDate(body.get("acctOpenDate").asText())
                .documentId(body.get("documentId").asText())
                .isOverSingleInwardLimit(false)
                .isOverDailyOutwardLimit(false)
                .isDelayProcess(false)
                .coreDate(resp.getCoreDate())
                .build();

        //企业账号没有bvn
        if(acctInfo.getClientNo().startsWith(BaseEnumType.E_CUSTTYPE.CORPORATE.getValue())) {
            acctInfo.setDocumentId("");
        }

        if(CommUtil.isNotNull(body.get("acctSeqNo"))) {
            acctInfo.setAcctSeqNo(body.get("acctSeqNo").asText());
        }

        if(CommUtil.isNotNull(body.get("ledgerBal"))) {
            acctInfo.setLedgerBal(body.get("ledgerBal").decimalValue());
        }

        if(CommUtil.isNotNull(body.get("actualBal"))) {
            acctInfo.setActualBal(body.get("actualBal").decimalValue());
        }

        if(CommUtil.isNotNull(body.get("maturityDate"))) {
            acctInfo.setMaturityDate(body.get("maturityDate").asText());
        }

        if(CommUtil.isNotNull(body.get("realRate"))) {
            acctInfo.setRealRate(body.get("realRate").decimalValue());
        }

        String classLevel = "1";
        if(CommUtil.isNotNull(body.get("classLevel"))) {
            //核心返回的calssLevel是两位数的，我们只需要用到第一位
            classLevel = body.get("classLevel").asText().substring(0,1);
        }
        acctInfo.setClassLevel(classLevel);

        //查询冻结信息
        JsonNode restras = body.findValue("fmRestraintTypeArray");
        if(CommUtil.isNotNull(restras)) {
            for(JsonNode rest : restras) {
                if (BaseEnumType.E_CORE_FREEZECODE.isIn(rest.get("restraintType").asText())) {
                    acctInfo.setFrezeType(rest.get("restraintType").asText());
                    acctInfo.setFrezeSeq(rest.get("resSeqNo").asText());
                    break;
                }
            }
        }

        //账户的余额限制
        if(acctInfo.getClientNo().startsWith(BaseEnumType.E_CUSTTYPE.CORPORATE.getValue())) {
            acctInfo.setBalLimit(new BigDecimal(appSystemParameterService.getValue(COR_BAL_LIMIT, classLevel)));
        } else {
            acctInfo.setBalLimit(new BigDecimal(appSystemParameterService.getValue(PER_BAL_LIMIT, classLevel)));
        }
//        if(debitCredit == null && CommUtil.isNull(tranAmt)) {
//            return acctInfo;
//        }

        //交易限额判断
        if(debitCredit != null && debitCredit == BaseEnumType.E_DEBITCREDIT.CREDIT) {

            BigDecimal singleInwardLimit;
            if(acctInfo.getClientNo().startsWith(BaseEnumType.E_CUSTTYPE.CORPORATE.getValue())) {
                singleInwardLimit = new BigDecimal(appSystemParameterService.getValue(COR_SINGLE_INWARD_LIMIT, classLevel));
            } else {
                String subKey = classLevel;
                if(isFinancialCust != null && isFinancialCust) {
                    subKey = LIMIT_SUB_KEY_5;
                }
                singleInwardLimit = new BigDecimal(appSystemParameterService.getValue(PER_SINGLE_INWARD_LIMIT, subKey));
            }
            // 超出单笔转入限额
            if(singleInwardLimit.compareTo(tranAmt) < 0) {
                acctInfo.setOverSingleInwardLimit(true);
            }
            return acctInfo;
        }
        //当日已经使用的限额
        BigDecimal dailyUsedOutwardLimit = BigDecimal.ZERO;
        CoreRespone custLimit = qryCustLimit(acctInfo.getClientNo(), acctNo, "", acctSeqNo);
        if(CommUtil.isNotNull(custLimit.getBody()) && CommUtil.isNotNull(custLimit.getBody().findValue("itemArray"))) {
            JsonNode limitArry = custLimit.getBody().findValue("itemArray");
            for(JsonNode limit : limitArry) {
                //PD代表日累计限额
                if(limit.get("limitType").asText().equals("PD")) {
//                    //核心配置的单日最大转出限额
//                    if(CommUtil.isNotNull(limit.get("limitMaxAmt"))) {
//                        dailyOutwardLimit = limit.get("limitMaxAmt").decimalValue();
//                    }
                    //只有当核心日期与返回的限额日期一致时才算当天的使用限额
                    if(CommUtil.isNotNull(limit.get("limitDate")) && custLimit.getCoreDate().equals(limit.get("limitDate").asText())
                            && CommUtil.isNotNull(limit.get("limitAmt")) ) {
                        dailyUsedOutwardLimit = limit.get("limitAmt").decimalValue();
                    }
                    break;
                }
            }
        }
        //参数配置的限额
        BigDecimal dailyOutwardLimit = new BigDecimal(9999999999L);
        if(CommUtil.isNotNull(tranAmt) && debitCredit == BaseEnumType.E_DEBITCREDIT.DEBIT) {

            BigDecimal dailyDealyLimit;
            if(acctInfo.getClientNo().startsWith(BaseEnumType.E_CUSTTYPE.CORPORATE.getValue())) {
                dailyDealyLimit = new BigDecimal(appSystemParameterService.getValue(COR_DAILY_DELAY_OUTWARD_LIMIT, classLevel));
            } else {
                dailyDealyLimit = new BigDecimal(appSystemParameterService.getValue(PER_DAILY_DELAY_OUTWARD_LIMIT, classLevel));
                String subKey = classLevel;
                if(isSignTfr != null && isSignTfr) {
                    subKey = LIMIT_SUB_KEY_4;
                    if(isFinancialCust != null && isFinancialCust) {
                        subKey = LIMIT_SUB_KEY_5;
                    }
                }
                //日转出限额由业务系统参数配置
                dailyOutwardLimit = new BigDecimal(appSystemParameterService.getValue(PER_DAILY_OUTWARD_LIMIT, subKey));
            }

            //是否需要延时处理
            if(dailyDealyLimit.compareTo(tranAmt.add(dailyUsedOutwardLimit)) < 0) {
                acctInfo.setDelayProcess(true);
            }

            if(dailyOutwardLimit.compareTo(tranAmt.add(dailyUsedOutwardLimit)) < 0) {
                acctInfo.setOverDailyOutwardLimit(true);
            }
        }
        acctInfo.setDailyOutLimit(dailyOutwardLimit);
        acctInfo.setDailyUsedOutLimit(dailyUsedOutwardLimit);
        return acctInfo;
    }

    /**
     * 查询核心单账户日累计转出限额
     * @param custNo
     * @return
     */
    public CoreRespone qryCustLimit(String custNo, String acctNo, String limitType) {
        return qryCustLimit(custNo, acctNo, limitType , null);
    }

    /**
     * 查询核心单账户日累计转出限额
     * @param custNo
     * @return
     */
    public CoreRespone qryCustLimit(String custNo, String acctNo, String limitType, String acctSeqNo) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("clientNo", custNo);
        req.put("baseAcctNo", acctNo);
        if(CommUtil.isNotNull(acctSeqNo)){
            req.put("acctSeqNo", acctSeqNo);
        }else{
            req.put("acctSeqNo",  BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
        }
        req.put("limitType", limitType);
        CoreRespone resp = post("/rb/nfin/tran/cumulative/query", req, false);
        if(!resp.isSuccess() && !resp.getErrorCode().equals(CORE_NORECORD_ERROR)) {
            throwError(resp.getErrorCode(), resp.getErrorMsg());
        }
        return resp;
    }
    /**
     * 冲正
     * @param referenceNo 交易单号
     * @return
     */
    public CoreRespone callCoreRevesl(String referenceNo) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("channelSeqNo", referenceNo);
        req.put("wipeAccount", "N");
        req.put("reversalReason", "nibss交易失败退款");
        req.put("skipCheckBranchFlag", "Y");
        return post("/rb/fin/channel/sigle/account", req, false);
    }

    /**
     * 根据存期计算到期日
     * @param term
     * @param termType D/M/Y
     * @param openDate YYYYMMDD
     * @return
     */
    public String calMaturityDate(String term, String termType, String openDate) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("term", term);
        req.put("termType", termType);
        req.put("acctOpenDate", openDate);
        req.put("ccy", ApConstants.BASE_CCY);
        CoreRespone resp = post("/rb/inq/term/period/calc", req);
        if(CommUtil.isNull(resp.getBody()) || CommUtil.isNull(resp.getBody().get("maturityDate"))) {
            throw new BusinessException(BusiErrorCode.FAILED);
        }
        return resp.getBody().get("maturityDate").asText();
    }

    /**
     * 直接通过core解冻，startDate 和 endDate 通过限制信息查询得到
     * @return
     */
    public CoreRespone banlanceUnfreeze(String acctNo, String freezeNo, String freezeType, String uid) {
        String prodId = CommUtil.getProdId(acctNo);

        Map<String, Object> req = Maps.newHashMap();
        req.put("baseAcctNo", acctNo);
        req.put("acctCcy", ApConstants.BASE_CCY);
        req.put("prodType", prodId);
        req.put("acctSeqNo", ApConstants.SUB_ACCOUNT_SEQ);
        req.put("acctResOperateType", ApConstants.UNFREEZE);
        req.put("resSeqNo", freezeNo);
        req.put("restraintType", freezeType);

        // 查询限制信息
        CoreRespone restraintInfo = queryRestraint(acctNo, freezeNo, uid);
        JsonNode body = restraintInfo.getBody();
        //通过冻结码来查询 只会有一条，没有或者有多条都可以报异常
        if(CommUtil.isNotNull(body) && CommUtil.isNotNull(body.get("restraintArray"))) {
            JsonNode rest = body.get("restraintArray").get(0);
            if (CommUtil.isNotNull(rest.get("startDate"))) {
                req.put("startDate", rest.get("startDate"));
            }
            if (CommUtil.isNotNull(rest.get("endDate"))) {
                req.put("endDate", rest.get("endDate"));
            }
        }
        return post("/rb/nfin/acct/restraint", req);
    }

    /**
     * 限制信息查询
     * @param baseAcctNo
     * @param resSeqNo
     * @return
     */
    public CoreRespone queryRestraint(String baseAcctNo, String resSeqNo, String uid) {
        Map<String, Object> req = Maps.newHashMap();
        req.put("resSeqNo", resSeqNo);
        req.put("baseAcctNo", baseAcctNo);
        req.put("seqNo", BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
        req.put("clientNo", uid);
        return post("/rb/inq/all/restraint", req, true);
    }

    public BigDecimal getTotalAsset(String uid) {
        return getTotalAsset(uid, null);
    }


    public BigDecimal getTotalAsset(String uid, String acctNo) {

        Map<String, Object> req = new HashMap<>();
        req.put("clientNo", uid);
        req.put("resultType", "1");
        req.put("queryFlag", "Y");
        if (CommUtil.isNotNull(acctNo)) {
            req.put("baseAcctNo", acctNo);
        }
        CoreRespone resp = post("/rb/inq/client/asset/query", req);
        JsonNode jsonBody = resp.getBody();

        BigDecimal assetTotalAmt = new BigDecimal(JsonNodeUtil.toString(jsonBody.get("assetTotalAmt")));

        JsonNode intNode = jsonBody.get("subArray");
        for (Iterator<JsonNode> it = intNode.elements(); it.hasNext(); ) {

            JsonNode node = it.next();
            if(node.get("prodType").asText().equals(BaseEnumType.E_COREPRODTYPE.PFLEX.getValue())) {

                ComCoreAcct.InterestOut interest = queryAcctInterest(node.get("prodType").asText()
                        ,CommUtil.getTrxnEnvs().getApp_uid()
                        ,node.get("baseAcctNo").asText()
                        ,node.get("acctSeqNo").asText()
                        ,ApConstants.BASE_CCY, false);

                //零钱通还需要在本金上加上应收利息
                assetTotalAmt = assetTotalAmt.add(new BigDecimal(interest.getAccrualInst()));
            }

        }

        //总资产余额
        return assetTotalAmt;
    }

    public void delInterestCache(String prodType, String clientNo,
                                 String acctNo, String acctSeq, String coreDate) {
        String key1 = CommUtil.getTrxnEnvs().getSystem_id() + ":" + "INTEREST:" + String.format("%s_%s_%s_%s_%s_%s",
                prodType,
                clientNo,
                acctNo,
                acctSeq,
                false,
                coreDate);
        String key2 = CommUtil.getTrxnEnvs().getSystem_id() + ":" + "INTEREST:" + String.format("%s_%s_%s_%s_%s_%s",
                prodType,
                clientNo,
                acctNo,
                acctSeq,
                true,
                coreDate);

        log.debug(">>>>>>>>>>>>>删除利息缓存key1: %s", key1);
        log.debug(">>>>>>>>>>>>>删除利息缓存key2: %s", key2);
        redisTemplate.delete(key1);
        redisTemplate.delete(key2);
    }

    public ComCoreAcct.InterestOut queryAcctInterestCache(String prodType, String clientNo,
                                                          String acctNo, String acctSeq, String currCode, boolean pastIntFlag, String coreDate) {
        String key = CommUtil.getTrxnEnvs().getSystem_id() + ":" + "INTEREST:" + String.format("%s_%s_%s_%s_%s_%s",
                prodType,
                clientNo,
                acctNo,
                acctSeq,
                pastIntFlag,
                coreDate);
        String retVal = redisTemplate.opsForValue().get(key);
        if (CommUtil.isNotNull(retVal)) {
            return JsonUtil.toObject(retVal, ComCoreAcct.InterestOut.class);
        }
        ComCoreAcct.InterestOut interest = queryAcctInterest(prodType, clientNo, acctNo, acctSeq, currCode, pastIntFlag);
        boolean needCache = false;
//        if(CommUtil.isNull(interest.getLastAccrualDate())) {
//            needCache = true;
//        } else {
            //只有当前核心日期和上日计提日期相差一天时才刷新当天的缓存，大于1天则说明核心计提还在处理中
        if(CommUtil.isNotNull(interest.getLastAccrualDate())) {
            int dateDiff = CommUtil.dateDiff("dd", interest.getLastAccrualDate(), coreDate);
            if (dateDiff == 1) {
                needCache = true;
            }
        }
//        }
        if(needCache) {
            redisTemplate.opsForValue().set(key, JsonUtil.toJsonStr(interest), 25, TimeUnit.HOURS);
        }
        return interest;
    }


    /**
     * 查询账户利息信息
     *
     * @param prodType
     * @param clientNo
     * @return
     */
    public ComCoreAcct.InterestOut queryAcctInterest(String prodType, String clientNo,
                                                 String acctNo, String acctSeq, String currCode, boolean pastIntFlag) {
        ComCoreAcct.AcctIntQuery acctIntQuery = new ComCoreAcct.AcctIntQuery();
        acctIntQuery.setClientNo(clientNo);
        acctIntQuery.setProdType(prodType);
        acctIntQuery.setBaseAcctNo(acctNo);
        acctIntQuery.setAcctSeqNo(acctSeq);
        acctIntQuery.setAcctCcy(currCode);
        acctIntQuery.setCcy(currCode);

        JsonNode body = post("/rb/inq/interest/accrued/details", acctIntQuery, "1","100",true).getBody();

        BigDecimal lastAccrualInst = BigDecimal.ZERO;
        BigDecimal accrualInst = BigDecimal.ZERO;
        BigDecimal totalInstTax = BigDecimal.ZERO;
        BigDecimal pastInterest = BigDecimal.ZERO;

        JsonNode intArray = body.findValue("intDetailArray");
        if (CommUtil.isNotNull(intArray)) {
            for (JsonNode inst : intArray) {
                //应记利息调增金额
                BigDecimal intAdj = new BigDecimal(JsonNodeUtil.toString(inst.get("intAdj"), "0"));

                //应记利息税
                BigDecimal taxAccrued = new BigDecimal(JsonNodeUtil.toString(inst.get("taxAccrued"), "0"));
                if(JsonNodeUtil.toString(inst.get("intClass")).equals("PDUE")) {
                    //当前逾期的税后应记利息
                    accrualInst = accrualInst.add(new BigDecimal(JsonNodeUtil.toString(inst.get("intPastDue"), "0")))
                            .add(intAdj)
                            .subtract(taxAccrued);
                } else {
                    //当前税后应记利息
                    accrualInst = accrualInst.add(new BigDecimal(JsonNodeUtil.toString(inst.get("intAccrued"), "0")))
                            .add(intAdj)
                            .subtract(taxAccrued);
                }

                //累计利息税
                totalInstTax = totalInstTax.add(new BigDecimal(JsonNodeUtil.toString(inst.get("taxPosted"), "0")));
                pastInterest = pastInterest.add(new BigDecimal(JsonNodeUtil.toString(inst.get("intPosted"), "0")));
            }
        }
            //已付利息 = 已付利息 - 累计利息税
        pastInterest = pastInterest.subtract(totalInstTax);

        String lastAccruedDate = "";
        JsonNode intHist = body.findValue("intAccrHistArray");
        if (CommUtil.isNotNull(intHist) && CommUtil.isNotNull(intHist.get(0))) {
            JsonNode intHistOne = intHist.get(0);
            //上日利息税
            BigDecimal taxAccruedCtd = new BigDecimal(JsonNodeUtil.toString(intHistOne.get("taxAccruedCtd"), "0"));
            //上日税后应记利息
            lastAccrualInst = new BigDecimal(JsonNodeUtil.toString(intHistOne.get("intAccruedCtd"), "0")).subtract(taxAccruedCtd);
            lastAccruedDate = JsonNodeUtil.toString(intHistOne.get("accrDate"));
        }

        return ComCoreAcct.InterestOut.builder()
                .accrualInst(accrualInst + "")
                .lastAccrualInst(lastAccrualInst + "")
                .totalInstTax(totalInstTax + "")
                .pastInterest(pastInterest + "")
                .lastAccrualDate(lastAccruedDate)
                .build();
    }

    /**
     * 查询账户是否进行过核心短信签约
     * @param acctNo
     * @return
     */
    public boolean isSignSms(String acctNo, String uid) {
        boolean isSign = false;
        Map<String, Object> req = new HashMap<>();
        req.put("clientNo", uid);
        req.put("agreementStatus", "A");
        req.put("businessId", "SMS");
        req.put("baseAcctNo", acctNo);

        JsonNode body = post("/rb/inq/agreement/unified", req).getBody();
        if(body != null && CommUtil.isNotNull(body.get("agreementStatus"))
                && body.get("agreementStatus").asText().equals("A")) {
            isSign = true;
        }
        return isSign;
    }

//    public CoreRespone creditNewEageAcct(String corporAcct, String newEageAcct, BigDecimal trxnAmt, String tranType) {
//        String callSeq = ApSeqUtil.genSeq(CALL_SEQ_CODE);
//        Map<String, Object> req = new HashMap<>();
//        req.put("origChannelSeqNo", callSeq);
//        req.put("baseAcctNo", corporAcct);
//        //appSystemParameterService.getValue(NEWEAGE_INTERNAL_ACCT, corporAcct)
//        req.put("othBaseAcctNo", newEageAcct);
//        req.put("acctCcy", ApConstants.BASE_CCY);
//        req.put("marketingProd", BaseEnumType.E_COREPRODTYPE.CCURRENT.getValue());
//        req.put("tranType", tranType);
//        req.put("tranAmt", trxnAmt);
//        req.put("eventType", "DEBT");//DEBT/CRET
//        req.put("othBranch", "090574");
//        req.put("prodType",BaseEnumType.E_COREPRODTYPE.CCURRENT.getValue());
//
//        List<Map<String, Object>> feeArry = new ArrayList<>();
//        req.put("servDetailArray", feeArry);
//        req.put("tranNote", "credit money to NewEageAcct");
//        req.put("othRealBaseAcctNo", newEageAcct);
//        req.put("othRealBankCode", "090574");
//        req.put("othRealBankName", "GOLDMAN MICROFINANCE BANK");
//        req.put("othRealTranName", "NewEageAcct");
//        return post("/rb/fin/channel/sigle/account", req, callSeq,false);
//    }
//
//    public CoreRespone debitNewEageAcct(String corporAcct, String newEageAcct, BigDecimal trxnAmt, String tranType) {
//        String callSeq = ApSeqUtil.genSeq(CALL_SEQ_CODE);
//        Map<String, Object> req = new HashMap<>();
//        req.put("origChannelSeqNo", callSeq);
//        req.put("baseAcctNo", newEageAcct);
//        req.put("othBaseAcctNo", corporAcct);
//        req.put("acctCcy", ApConstants.BASE_CCY);
//        req.put("marketingProd", BaseEnumType.E_COREPRODTYPE.CCURRENT.getValue());
//        req.put("tranType", tranType);
//        req.put("tranAmt", trxnAmt);
//        req.put("eventType", "DEBT");//DEBT/CRET
//        req.put("othBranch", "090574");
//        req.put("prodType",BaseEnumType.E_COREPRODTYPE.LOAN_NEWEDGE_INTERNAL.getValue());
//
//        List<Map<String, Object>> feeArry = new ArrayList<>();
//        req.put("servDetailArray", feeArry);
//        req.put("tranNote", "credit money to NewEageAcct");
//        req.put("othRealBaseAcctNo", corporAcct);
//        req.put("othRealBankCode", "090574");
//        req.put("othRealBankName", "GOLDMAN MICROFINANCE BANK");
//        req.put("othRealTranName", "NewEageAcct");
//        return post("/rb/fin/channel/sigle/account", req, callSeq,false);
//    }

    /**
     * 贷款记账 放款/还款
     * @param callSeq
     * @param busiSeq
     * @param fee
     * @param feeType
     * @param direct
     * @param trxnAmt
     * @param remark
     * @return
     */
    public CoreRespone loanAccounting(String callSeq, String busiSeq,
                                   ComCoreAcct.AcctInfo acct, ComCoreAcct.AcctInfo oppAcct,
                                   String fee, String feeType,
                                   BaseEnumType.E_DEBITCREDIT direct,
                                   BigDecimal trxnAmt, String remark) {

        Map<String, Object> req = new HashMap<>();
        req.put("origChannelSeqNo", busiSeq);
        req.put("baseAcctNo", acct.getBaseAcctNo());
        String prodId = CommUtil.getProdId(acct.getBaseAcctNo());
        String internal = appSystemParameterService.getValue(NEWEAGE_INTERNAL_ACCT, oppAcct.getBaseAcctNo());
        if(CommUtil.isNull(internal)) {
            throw new BankBusiException(CmErrorCode.ACCT_NOT_EXSIT, new String[]{NEWEAGE_INTERNAL_ACCT});
        }
        req.put("othBaseAcctNo", internal);
        req.put("acctCcy", ApConstants.BASE_CCY);
        req.put("marketingProd", prodId);
        req.put("tranType", direct == BaseEnumType.E_DEBITCREDIT.CREDIT ? BaseEnumType.E_TRAN_TYPE.ADD_MONEY.getValue() : BaseEnumType.E_TRAN_TYPE.TRANSFER.getValue());
        req.put("tranAmt", trxnAmt);
        req.put("eventType", direct == BaseEnumType.E_DEBITCREDIT.CREDIT ? "CRET" : "DEBT");//DEBT/CRET
        req.put("othBranch", "090574");
        req.put("prodType",prodId);

        List<Map<String, Object>> feeArry = new ArrayList<>();
        BigDecimal feeAmt = BigDecimal.ZERO;
        if(CommUtil.isNotNull(fee)) {
            feeAmt = new BigDecimal(fee);
        }
        if(CommUtil.isNotNull(feeType) && feeAmt.compareTo(BigDecimal.ZERO) > 0) {
            Map<String, Object> fe = new HashMap<>();
            fe.put("feeType", feeType);
            fe.put("feeCcy", ApConstants.BASE_CCY);
            fe.put("chargeMode", ApConstants.TRANSFER_ACCOUNT);
            fe.put("feeAmt", fee);
            fe.put("chargeToBaseAcctNo", acct.getBaseAcctNo());
            fe.put("chargeToProdType", prodId);
            fe.put("chargeToCcy", ApConstants.BASE_CCY);
            fe.put("chargeToAcctSeqNo", BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
            feeArry.add(fe);
        }
        req.put("servDetailArray", feeArry);
        req.put("tranNote", remark);
        req.put("othRealBaseAcctNo", oppAcct.getBaseAcctNo());
        req.put("othRealBankCode", "090574");
        req.put("othRealBankName", "GOLDMAN MICROFINANCE BANK");
        req.put("othRealTranName", oppAcct.getAcctName());
        CoreRespone post = post("/rb/fin/channel/sigle/account", req, callSeq, false);
        if(post.isTimeOut()){
            log.error("Core Sigle Account Error, Scenes: CoreUtil.loanAccounting. callSeq:{}", callSeq);
        }
        return post;
    }

    public CoreRespone flexWithDraw(String custNo, String acctNo, String acctName, BigDecimal trxnAmt) {
        //需要做零钱通结清
        Map<String, Object> req = new HashMap<>();
        req.put("clientNo", custNo);
        if(custNo.startsWith(BaseEnumType.E_CUSTTYPE.PERSON.getValue())) {
            req.put("prodType", BaseEnumType.E_COREPRODTYPE.PFLEX.getValue());
        } else {
            req.put("prodType", BaseEnumType.E_COREPRODTYPE.CFLEX.getValue());
        }
        req.put("baseAcctNo", acctNo);
        req.put("acctSeqNo", BaseEnumType.E_ACCT_SEQ.PFLEX.getValue());
        req.put("tranCcy", ApConstants.BASE_CCY);
        req.put("tranType", BaseEnumType.E_TRAN_TYPE.WITHDRAW.getValue());
        req.put("tranAmt", trxnAmt);
        //01-实时到账
        req.put("tranMethod", "01");
        req.put("othBaseAcctNo", acctNo);
        req.put("othAcctSeqNo", BaseEnumType.E_ACCT_SEQ.PCURRENT.getValue());
        if(custNo.startsWith(BaseEnumType.E_CUSTTYPE.PERSON.getValue())) {
            req.put("othProdType", BaseEnumType.E_COREPRODTYPE.PCURRENT.getValue());
        } else {
            req.put("othProdType", BaseEnumType.E_COREPRODTYPE.CCURRENT.getValue());
        }
        req.put("othAcctCcy", ApConstants.BASE_CCY);
        req.put("othAcctName", acctName);
        req.put("acctCcy", ApConstants.BASE_CCY);
        req.put("netInterestAmt", "");
        CoreRespone resp = post("/rb/fin/current/zmp", req);
        delInterestCache(BaseEnumType.E_COREPRODTYPE.PFLEX.getValue(), custNo, acctNo, BaseEnumType.E_ACCT_SEQ.PFLEX.getValue(), resp.getCoreDate());
        return resp;
    }

    public CoreRespone queryCoreTxnStatus(String trxnSeq) {
        String trxnDate = trxnSeq.replace(CommUtil.getTrxnEnvs().getSystem_id(), "").substring(0, 8);
        return queryCoreTxnStatus(trxnSeq, trxnSeq, trxnDate,false);
    }

    /**
     * 查询核心交易状态
     * @param trxnSeq
     * @return
     */
    public CoreRespone queryCoreTxnStatus(String trxnSeq, String coreDate) {
        String trxnDate = trxnSeq.replace(CommUtil.getTrxnEnvs().getSystem_id(), "").substring(0, 8);
        return queryCoreTxnStatus(trxnSeq, trxnSeq, trxnDate, coreDate, false);
    }

//    public static void main(String[] args) {
//        System.out.println(CommUtil.in(replace("/rb/fin/channel/sigle/account"), Arrays.asList(FIN_TRANPATH)));
//    }
}
